﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Shapes;
using BTM_Client_Application.SupportClasses;
using BTM_Client_DBwork.Constants;
using Client_Server_Interface;
using GMap.NET;
using GMap.NET.CacheProviders;
using GMap.NET.MapProviders;
using GMap.NET.WindowsPresentation;

namespace BTM_Client_Application
{
    /// <summary>
    /// Логика взаимодействия для MapControl.xaml
    /// </summary>
    public partial class MapControl
    {
        public delegate void SetMapPositionDelegate(PointLatLng point);
        public List<StationElement> Stations { get; private set; }
        private Dictionary<string, MarkersEditionHelper> _movedMarkers;         //Список передвинутых маркеров во время редактирования
        private Dictionary<string, MarkersEditionHelper> _removedLocalMarkers;
        private Dictionary<string, PointLatLng> _serverMarkerPositions;
        private ZoomControl _zoom;
        private int _minMaxCompare;
        private double _mapZoom;
        private readonly double _scaling;       //Коэфициаент масштабирования маркеров при изменении уровня приближения
        private bool _allowEditMarkers;
        private bool _resetOneState = false;
        private bool _takeLocalMarkerPositions = false;     //Определет, откуда брать позиции маркеров 
                                                    //(true - локальная БД, false - данные с сервера)

        public bool AllowEditMarkers
        {
            get { return _allowEditMarkers; }
            set
            {
                if (_allowEditMarkers == value)
                    return;
                //Включаем редактирование
                if (value)
                {
                    _movedMarkers = new Dictionary<string, MarkersEditionHelper>();
                    _removedLocalMarkers = new Dictionary<string, MarkersEditionHelper>();
                    //GmapBox.MouseMove += GmapBoxOnMouseMove;
                    MarkersMovePermission(true);
                    GmapBox.MouseDown += GmapBoxOnMouseDown;
                    EditPanel.Visibility = Visibility.Visible;
                }
                //Отключаем редактирование
                else
                {
                    RollbackMarkerPositions();
                    ResetOneStateChange(false);
                    _movedMarkers = null;
                    _removedLocalMarkers = null;
                    //GmapBox.MouseMove -= GmapBoxOnMouseMove;
                    MarkersMovePermission(false);
                    GmapBox.MouseDown -= GmapBoxOnMouseDown;
                    EditPanel.Visibility = Visibility.Hidden;
                    FillStationsInfo();
                }
                _allowEditMarkers = value;
            }
        }

        void MarkersMovePermission(bool permit)
        {
            if (permit)
            {
                GmapBox.MouseMove += GmapBoxOnMouseMove;
            }
            else
            {
                GmapBox.MouseMove -= GmapBoxOnMouseMove;
            }
        }

        private GMapMarker _selectedMarker;

        //private const double Lat = 56.2555574519306;
        //private const double Lng = 22.8076171875;
        //private const double WidthLng = 10.469970703125;
        //private const double WidthLat = 5.26945851853705;
        private const double Lat = 56.2;
        private const double Lng = 22.6;
        private const double WidthLng = 10.29;
        private const double WidthLat = 5.1;
        private static readonly RectLatLng BelarusBounds = new RectLatLng(Lat, Lng, WidthLng, WidthLat);

        private MapControl(double zoom)
        {
            _mapZoom = zoom;
            InitializeComponent();
            MapMarker.UpdateMapIcon = UseMainThread;
            EditPanelChangeState(false);
            //EditPanel.IsEnabled = false;
        }

        public MapControl(double zoom, double scaling)
            : this(zoom)
        {
            _scaling = scaling;
            Stations = AliveInfo.MegaDeviceTree.Stations;
            FillStationsInfo();
        }

        private void MapControl_OnInitialized(object sender, EventArgs e)
        {

            SaveButton.Content = Res.Rm.GetString("Save", AliveInfo.CurrentCulture);
            RollbackButton.Content = Res.Rm.GetString("Rollback", AliveInfo.CurrentCulture);
            ResetOneButton.Content = Res.Rm.GetString("ResetOne", AliveInfo.CurrentCulture);

            EditPanel.Visibility = Visibility.Hidden;
            //GMapProvider.WebProxy = WebRequest.GetSystemWebProxy();
            //GMapProvider.WebProxy.Credentials = CredentialCache.DefaultCredentials;
            GmapBox.MapProvider = GoogleMapProvider.Instance;
            GmapBox.CacheLocation = "cachefolder";
            //GmapBox.FillEmptyTiles = true;
            GmapBox.EmptyMapBackground = new SolidColorBrush(Colors.Gray);
            GmapBox.EmptytileBrush = new SolidColorBrush(Colors.GreenYellow);
            GmapBox.EmptyTileText = new FormattedText(Res.Rm.GetString("EmptyTileText", AliveInfo.CurrentCulture),
                CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                new Typeface(new FontFamily(), FontStyles.Normal, FontWeights.Regular, new FontStretch()), 16,
                new SolidColorBrush(Colors.Blue));

            GMaps.Instance.Mode = AccessMode.CacheOnly;

            try
            {
                var path = AliveInfo.DBwork.GetParameterValue(OptionNames.MAP_CACHE_LOCATION);

                if (!File.Exists(path + Res.DbCacheInFolderLocation))
                {
                    AliveInfo.ClientEvents.AddEvent(Res.Rm.GetString("Warning"),
                                                    Res.Rm.GetString("MapCacheLocationNotSet") + ".\r\n" +
                                                    Res.Rm.GetString("NoConnectionWithMapDb") + '.');
                }
                else
                {
                    var sc = new SQLitePureImageCache { CacheLocation = path };
                    GMaps.Instance.SecondaryCache = sc;
                }
                
            }
            catch (Exception)
            {
                AliveInfo.ClientEvents.AddEvent(Res.Rm.GetString("Warning"),
                                                Res.Rm.GetString("MapCacheLocationNotSet") + ".\r\n" +
                                                Res.Rm.GetString("NoConnectionWithMapDb") + '.');
            }

            //GMaps.Instance.Mode = AccessMode.CacheOnly;
            //GMaps.Instance.ImportFromGMDB("Data.gmdb");
            GmapBox.Position = BelarusBounds.LocationMiddle;
            //GmapBox.SetPositionByKeywords("Minsk");
            //GmapBox.MaxZoom = 17;
            GmapBox.MaxZoom = 14;
            GmapBox.MinZoom = 7;
            _minMaxCompare = GmapBox.MaxZoom - GmapBox.MinZoom;
            _zoom = new ZoomControl(1, _minMaxCompare + 1);
            GmapBox.Zoom = _mapZoom;
            GmapBox.OnMapZoomChanged += GmapBoxOnOnMapZoomChanged;
            GmapBox.ShowCenter = false;
            GmapBox.BoundsOfMap = BelarusBounds;
            GmapBox.OnMapDrag += GmapBoxOnOnMapDrag;
            GmapBox.OnMapZoomChanged += () =>
                {
                    ChangeMiniMapSelectedZone(new RectLatLng(GmapBox.ViewArea.LocationTopLeft, GmapBox.ViewArea.Size));
                    _zoom.SetTick((int)GmapBox.Zoom - _minMaxCompare + 1);
                };
            _zoom.TickChanged += tick =>
                {
                    GmapBox.Zoom = tick + _minMaxCompare - 1;
                };
            GmapBox.OnPositionChanged += GmapBoxOnOnPositionChanged;
            /*
            var font = new FontFamily("Segoe UI, Verdana");
            for (int i = 2; i <= comp + 2; i++)
            {
                ZoomControl.RowDefinitions.Add(new RowDefinition());
                var b = new Button
                    {
                        //Content = comp - i + 3,
                        VerticalAlignment = VerticalAlignment.Center,
                        HorizontalAlignment = HorizontalAlignment.Stretch,
                        Background = Res.AccentBlueBrush,
                        Foreground = new SolidColorBrush(Colors.White),
                    };
                
                b.Content = new TextBlock
                    {
                        Text = (comp - i + 3).ToString(CultureInfo.InvariantCulture),
                        VerticalAlignment = VerticalAlignment.Center,
                        HorizontalAlignment = HorizontalAlignment.Center,
                        Foreground = new SolidColorBrush(Colors.White),
                        FontFamily = font
                    };
                b.Click += (o, args) =>
                    {
                        var zoomOnButton = ((TextBlock)((Button)o).Content).Text;
                        GmapBox.Zoom = int.Parse(zoomOnButton) + GmapBox.MinZoom - 1;
                        ZoomLevelBox.Text = zoomOnButton;
                    };
                Grid.SetRow(b, i);
                ZoomControl.Children.Add(b);
            }

            ZoomControl.RowDefinitions.Add(new RowDefinition());
            var but = new Button
                {
                Content = "-",
                VerticalAlignment = VerticalAlignment.Bottom,
                HorizontalAlignment = HorizontalAlignment.Stretch,
                Background = new SolidColorBrush(Colors.Orange)
            };
            Grid.SetRow(but, comp + 3);
            ZoomControl.Children.Add(but);
            */
            ZoomControl.Children.Add(_zoom);
            InitMiniMap();
            ChangeMiniMapSelectedZone(new RectLatLng(GmapBox.ViewArea.LocationTopLeft, GmapBox.ViewArea.Size));
        }

        public void DisconnectMap()
        {
            GMaps.Instance.CancelTileCaching();
            GMaps.Instance.DisableTileHost();
        }

        private void GmapBoxOnOnPositionChanged(PointLatLng point)
        {
            ChangeMiniMapSelectedZone(new RectLatLng(GmapBox.ViewArea.LocationTopLeft, GmapBox.ViewArea.Size));
        }

        private void GmapBoxOnOnMapDrag()
        {
            ChangeMiniMapSelectedZone(new RectLatLng(GmapBox.ViewArea.LocationTopLeft, GmapBox.ViewArea.Size));
        }

        private Point _mouseDownPosition = new Point(0, 0);
        private GPoint _markerStartPosition = new GPoint(0, 0);

        private void GmapBoxOnMouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            if (_selectedMarker == null)
            {
                return;
            }
            _mouseDownPosition = Mouse.GetPosition(GmapBox);
            _markerStartPosition = GmapBox.FromLatLngToLocal(_selectedMarker.Position);
        }

        private void GmapBoxOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            if (_selectedMarker == null || _resetOneState)
            {
                return;
            }
            if (mouseEventArgs.LeftButton ==  MouseButtonState.Pressed)
            {
                if (_selectedMarker.Shape.Visibility == Visibility.Visible)
                {
                    var p = Mouse.GetPosition(GmapBox);
                    var dx = p.X - _mouseDownPosition.X;
                    var dy = p.Y - _mouseDownPosition.Y;

                    _selectedMarker.Position = GmapBox.FromLocalToLatLng((int)(_markerStartPosition.X + dx), (int)(_markerStartPosition.Y + dy));
                    
                }
            }
        }

        private void GmapBoxOnOnMapZoomChanged()
        {
            foreach (GMapMarker marker in GmapBox.Markers)
            {
                var t = marker.Shape as StackPanel;
                if (t != null)
                {
                    var m = t.Tag as MapMarker;
                    _mapZoom = GmapBox.Zoom;
                    if (m != null)
                        m.SetImage(Res.ResizeImage(Res.MapIconName, (int)(128 * GmapBox.Zoom / _scaling), (int)(128 * GmapBox.Zoom / _scaling)));
                }
            }
        }

        public void FillStationsInfo()
        {
            GmapBox.Markers.Clear();
            try
            {
                _takeLocalMarkerPositions =
                    bool.Parse(AliveInfo.DBwork.GetParameterValue(OptionNames.USE_LOCAL_MARKER_POSITIONS));
            }
            catch (Exception)
            {
                _takeLocalMarkerPositions = false;
                AliveInfo.DBwork.SetParameter(OptionNames.USE_LOCAL_MARKER_POSITIONS, OptionDefaultValue.USE_LOCAL_MARKER_POSITIONS);
            }
            _serverMarkerPositions = new Dictionary<string, PointLatLng>(Stations.Count);
            foreach (StationElement station in Stations)
            {
                if(station.Info.Coordinates == "")
                    continue;
                var serverPos = new PointLatLng(
                            Support.CoordinatesToDouble(station.Info.CoordinatesB),
                            Support.CoordinatesToDouble(station.Info.CoordinatesA));
                _serverMarkerPositions.Add(station.Info.Station, serverPos);
                PointLatLng pos;
                if (_takeLocalMarkerPositions)
                {
                    try
                    {
                        var coords = AliveInfo.DBwork.GetMapMarkerPosition(station.Info.Station);
                        pos = new PointLatLng(coords.Key, coords.Value);
                    }
                    catch (Exception)
                    {
                        pos = serverPos;
                    }
                }
                else
                {
                    pos = serverPos;
                }
                var marker = new GMapMarker(pos) {Shape = station.Marker};
                StationElement station1 = station;

                marker.Shape.MouseDown += (sender, args) =>
                    {
                        if (!AllowEditMarkers && !_resetOneState)
                            return;
                        //Сбрасываем положение одного маркера
                        if (_resetOneState)
                        {
                            SetServerMarkerPosition(station1.Info, marker);
                            return;
                        }
                        //Добавляем в список передвинутых
                        if (!_movedMarkers.ContainsKey(station1.Info.Station))
                        {
                            _movedMarkers.Add(station1.Info.Station,
                                              new MarkersEditionHelper {StartPosition = marker.Position, Marker = marker, AfterEditPosition = marker.Position});
                            if (_removedLocalMarkers.ContainsKey(station1.Info.Station))
                                _removedLocalMarkers.Remove(station1.Info.Station);

                        }
                        _selectedMarker = marker;
                    };
                marker.Shape.MouseUp += (sender, args) =>
                    {
                        if (!AllowEditMarkers || _resetOneState)
                            return;
                        _movedMarkers[station1.Info.Station].AfterEditPosition = marker.Position;
                        _selectedMarker = null;
                        if (_movedMarkers.Count > 0)
                            EditPanelChangeState(true);
                            //EditPanel.IsEnabled = true;
                    };
                GmapBox.Markers.Add(marker);
                GmapBox.IgnoreMarkerOnMouseWheel = true;
            }
        }

        void SetServerMarkerPosition(DeviceTreeItem.StationInfo station, GMapMarker marker)
        {
            if (_removedLocalMarkers.ContainsKey(station.Station))
                return;
            var serverPos = new PointLatLng(
                            Support.CoordinatesToDouble(station.CoordinatesB),
                            Support.CoordinatesToDouble(station.CoordinatesA));

            if (_movedMarkers.ContainsKey(station.Station))
                _movedMarkers.Remove(station.Station);
            _removedLocalMarkers.Add(station.Station,
                new MarkersEditionHelper {Marker = marker, StartPosition = marker.Position});
            marker.Position = serverPos;
            if(_removedLocalMarkers.Count > 0)
                EditPanelChangeState(true);
            //AliveInfo.DBwork.RemoveMapMarkerPosition(station.Station);
        }

        public void CenterPosition(PointLatLng point)
        {
            if (Math.Abs(point.Lat - 0) < 0.1  && Math.Abs(point.Lng - 0) < 0.1)
                return;
            GmapBox.Position = point;
        }

        public void UseMainThread(VoidDelegate del)
        {
            Dispatcher.Invoke(new Action(() => del()));
        }

        private void SaveButton_OnClick(object sender, RoutedEventArgs e)
        {
            SaveMarkerPositions();
            ResetOneStateChange(false);
            EditPanelChangeState(false);
            //EditPanel.IsEnabled = false;
            ResetOneButton.IsEnabled = true;
        }

        private void RollbackButton_OnClick(object sender, RoutedEventArgs e)
        {
            RollbackMarkerPositions();
            EditPanelChangeState(false);
            //EditPanel.IsEnabled = false;
        }

        private void SaveMarkerPositions()
        {
            foreach (var marker in _movedMarkers)
            {
                AliveInfo.DBwork.SetMapMarkerPosition(marker.Key, marker.Value.AfterEditPosition.Lat,
                                                      marker.Value.AfterEditPosition.Lng);
            }

            foreach (var marker in _removedLocalMarkers)
            {
                AliveInfo.DBwork.RemoveMapMarkerPosition(marker.Key);
            }

            _movedMarkers.Clear();
            _removedLocalMarkers.Clear();
            EditPanelChangeState(false);
            //EditPanel.IsEnabled = false;
        }

        private void RollbackMarkerPositions()
        {
            foreach (var markerPosition in _movedMarkers)
            {
                markerPosition.Value.AfterEditPosition = markerPosition.Value.StartPosition;
                markerPosition.Value.Marker.Position = markerPosition.Value.StartPosition;
            }

            foreach (var markerPosition in _removedLocalMarkers)
            {
                markerPosition.Value.Marker.Position = markerPosition.Value.StartPosition;
            }

            _removedLocalMarkers.Clear();
            
            FillStationsInfo();
        }

        private void PrefetchButton_OnClick(object sender, RoutedEventArgs e)
        {
            
            for (int i = 14; i < 18; i++)
            {
                var obj = new TilePrefetcher {Owner = AliveInfo.MainWindow, ShowCompleteMessage = false};
                obj.Start(BelarusBounds, i, GmapBox.MapProvider, 100);
            }
            MessageBox.Show("prefetch done");
        }

        private void ResetOneButton_OnClick(object sender, RoutedEventArgs e)
        {
            ResetOneStateChange(!_resetOneState);
        }

        void ResetOneStateChange(bool resetStateOn)
        {
            if (!resetStateOn)
            {
                ResetOneButton.Content = Res.Rm.GetString("ResetOne", AliveInfo.CurrentCulture);
                //MarkersMovePermission(true);
            }
            else
            {
                ResetOneButton.Content = Res.Rm.GetString("Cancel", AliveInfo.CurrentCulture);
                //MarkersMovePermission(false);
            }
            _resetOneState = resetStateOn;
        }

        void EditPanelChangeState(bool isEnabled)
        {
            SaveButton.IsEnabled = isEnabled;
            RollbackButton.IsEnabled = isEnabled;
        }
    }

    class MarkersEditionHelper
    {
        public GMapMarker Marker { get; set; }
        public PointLatLng StartPosition { get; set; }
        public PointLatLng AfterEditPosition { get; set; }
    }

    public delegate void UpdateMapIconDelegate(VoidDelegate del);
}
